﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;

namespace OpenCvSharp
{
    public abstract class CvDispObj : INotifyPropertyChanged
    {
        internal const double OPERATOR_OFFSET = 10d;

        private bool _isControlCoordSystem = false;
        private Color _color = Color.Red;
        private float _penWidth = 1f;

        [DefaultValue(false)]
        [RefreshProperties(RefreshProperties.All)]
        public virtual bool IsControlCoordSystem
        {
            get
            {
                return _isControlCoordSystem;
            }
            set
            {
                if (_isControlCoordSystem != value)
                {
                    _isControlCoordSystem = value;
                    OnPropertyChanged(nameof(IsControlCoordSystem));
                }
            }
        }

        [DefaultValue(typeof(Color), "Red")]
        [RefreshProperties(RefreshProperties.All)]
        public virtual Color Color
        {
            get
            {
                return _color;
            }
            set
            {
                if (_color != value)
                {
                    _color = value;
                    OnPropertyChanged(nameof(Color));
                }
            }
        }

        [DefaultValue(1f)]
        [RefreshProperties(RefreshProperties.All)]
        public virtual float PenWidth
        {
            get
            {
                return _penWidth;
            }
            set
            {
                if (_penWidth != value && value > 0f)
                {
                    _penWidth = value;
                    OnPropertyChanged(nameof(PenWidth));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public abstract void OnPaint(Graphics g, Mat imageMatrix);

        protected IList<CvPoint> TransformPoints(Mat imageMatrix, params CvPoint[] points)
        {
            if (!imageMatrix.IsValid())
            {
                return points;
            }

            List<CvPoint> dst = new List<CvPoint>(points.Length);
            Cv2.Transform(InputArray.Create(points, MatType.CV_64FC2), OutputArray.Create(dst),imageMatrix);
            return dst;
        }

        protected CvPoint[] DrawLineCore(Graphics g, Pen p, Mat imageMatrix, CvLine line, bool drawArrow = false)
        {
            var ps = TransformPoints(imageMatrix, line.BeginPoint, line.EndPoint );

            g.DrawLine(p, ps[0], ps[1]);
            if (drawArrow)
            {
                double angle = CvOperator.LineOrientation(ps[0].X, ps[0].Y, ps[1].X, ps[1].Y);
                double length = CvOperator.DistancePP(ps[0], ps[1]);
                length /= 2d;
                if (OPERATOR_OFFSET < length)
                {
                    length = OPERATOR_OFFSET;
                }

                angle -= 157.5d;
                CvPoint pa = CvOperator.MovePoint(ps[1], angle, length);
                angle -= 45d;
                CvPoint pb = CvOperator.MovePoint(ps[1], angle, length);

                g.DrawLine(p, pa, pb);
                g.DrawLine(p, pa, ps[1]);
                g.DrawLine(p, pb, ps[1]);
            }
            return ps.ToArray();
        }

        protected void DrawCrossCore(Graphics g, Pen p, Mat imageMatrix, CvPoint center, double size = OPERATOR_OFFSET, double angle = 45d)
        {
            var ps = TransformPoints(imageMatrix, center);

            double rad = CvOperator.ToRad(angle);
            size /= 2d;

            double sin = Math.Sin(rad) * size;
            double cos = Math.Cos(rad) * size;
            double x1 = ps[0].X - cos;
            double y1 = ps[0].Y - sin;
            double x2 = ps[0].X + cos;
            double y2 = ps[0].Y + sin;
            g.DrawLine(p, (float)x1, (float)y1, (float)x2, (float)y2);

            rad -= CvOperator.ToRad(90d);
            sin = Math.Sin(rad) * size;
            cos = Math.Cos(rad) * size;
            x1 = ps[0].X - cos;
            y1 = ps[0].Y - sin;
            x2 = ps[0].X + cos;
            y2 = ps[0].Y + sin;
            g.DrawLine(p, (float)x1, (float)y1, (float)x2, (float)y2);
        }

        protected CvPoint[] DrawArcCore(Graphics g, Pen p, Mat imageMatrix, CvCircle circle, double startAngle = 0d, double sweepAngle = 360d, bool drawHandle = false)
        {
            var ps = TransformPoints(imageMatrix, new CvPoint(circle.CenterX - circle.Radius, circle.CenterY - circle.Radius), new CvPoint(circle.CenterX + circle.Radius, circle.CenterY + circle.Radius));
            g.DrawArc(p, (float)ps[0].X, (float)ps[0].Y, (float)(ps[1].X - ps[0].X), (float)(ps[1].Y - ps[0].Y), (float)startAngle, (float)sweepAngle);
            CvPoint[] handlePos = null;
            if (drawHandle)
            {
                handlePos = new CvPoint[3];
                handlePos[0] = new CvPoint((ps[1].X + ps[0].X) / 2d, (ps[1].Y + ps[0].Y) / 2d);
                DrawCrossCore(g, p, null, handlePos[0]);
                handlePos[1] = CvOperator.MovePoint(handlePos[0], startAngle, (ps[1].X - ps[0].X) / 2d);
                handlePos[2] = CvOperator.MovePoint(handlePos[0], startAngle + sweepAngle, (ps[1].X - ps[0].X) / 2d);

                for (int i = 1; i < handlePos.Length; i++)
                {
                    g.FillEllipse(Brushes.Cyan, (float)(handlePos[i].X - OPERATOR_OFFSET / 4d), (float)(handlePos[i].Y - OPERATOR_OFFSET / 4d), (float)(OPERATOR_OFFSET / 2d), (float)(OPERATOR_OFFSET / 2d));
                }
            }
            return handlePos;
        }

        protected CvPoint[] DrawRingCore(Graphics g, Pen p, Mat imageMatrix, CvCircle circle, double ringWidth, double startAngle = 0d, double sweepAngle = 360d, bool drawHandle = false)
        {
            ringWidth /= 2d;
            var ps = TransformPoints(imageMatrix, 
                new CvPoint(circle.CenterX - circle.Radius - ringWidth, circle.CenterY - circle.Radius - ringWidth),
                new CvPoint(circle.CenterX + circle.Radius + ringWidth, circle.CenterY + circle.Radius + ringWidth),
                new CvPoint(circle.CenterX - circle.Radius + ringWidth, circle.CenterY - circle.Radius + ringWidth),
                new CvPoint(circle.CenterX + circle.Radius - ringWidth, circle.CenterY + circle.Radius - ringWidth),
                CvOperator.MovePoint(circle.Center, startAngle, circle.Radius - ringWidth),
                CvOperator.MovePoint(circle.Center, startAngle, circle.Radius + ringWidth),
                CvOperator.MovePoint(circle.Center, startAngle + sweepAngle, circle.Radius - ringWidth),
                CvOperator.MovePoint(circle.Center, startAngle + sweepAngle, circle.Radius + ringWidth));

            g.DrawArc(p, (float)ps[0].X, (float)ps[0].Y, (float)(ps[1].X - ps[0].X), (float)(ps[1].Y - ps[0].Y), (float)startAngle, (float)sweepAngle);
            g.DrawArc(p, (float)ps[2].X, (float)ps[2].Y, (float)(ps[3].X - ps[2].X), (float)(ps[3].Y - ps[2].Y), (float)startAngle, (float)sweepAngle);
            g.DrawLine(p, ps[4], ps[5]);
            g.DrawLine(p, ps[6], ps[7]);

            CvPoint[] handlePos = null;
            if (drawHandle)
            {
                handlePos = new CvPoint[7];
                handlePos[0] = new CvPoint((ps[0].X + ps[1].X) / 2d, (ps[0].Y + ps[1].Y) / 2d);
                DrawCrossCore(g, p, null, handlePos[0]);
                handlePos[1] = ps[4];
                handlePos[2] = ps[5];
                handlePos[3] = ps[6];
                handlePos[4] = ps[7];
                handlePos[5] = new CvPoint((ps[4].X + ps[5].X) / 2d, (ps[4].Y + ps[5].Y) / 2d);
                handlePos[6] = new CvPoint((ps[6].X + ps[7].X) / 2d, (ps[6].Y + ps[7].Y) / 2d);
                for (int i = 1; i < handlePos.Length; i++)
                {
                    g.FillEllipse(Brushes.Cyan, (float)(handlePos[i].X - OPERATOR_OFFSET / 4d), (float)(handlePos[i].Y - OPERATOR_OFFSET / 4d), (float)(OPERATOR_OFFSET / 2d), (float)(OPERATOR_OFFSET / 2d));
                }
            }
            return handlePos;
        }

        protected CvPoint[] DrawRectCore(Graphics g, Pen p, Mat imageMatrix, CvRect rect, bool drawHandle = false)
        {
            var ps = TransformPoints(imageMatrix, rect.Location, new CvPoint(rect.X + rect.Width,rect.Y + rect.Height));

            g.DrawRectangle(p, (float)ps[0].X, (float)ps[0].Y, (float)(ps[1].X - ps[0].X), (float)(ps[1].Y - ps[0].Y));
            CvPoint[] handlePos = null;
            if (drawHandle)
            {
                handlePos = new CvPoint[5];
                handlePos[0] = new CvPoint((ps[0].X + ps[1].X) / 2d, (ps[0].Y + ps[1].Y) / 2d);
                DrawCrossCore(g, p, null, handlePos[0]);
                handlePos[1] = ps[0];
                handlePos[2] = new CvPoint(ps[1].X, ps[0].Y);
                handlePos[3] = ps[1];
                handlePos[4] = new CvPoint(ps[0].X, ps[1].Y);

                for (int i = 1; i < handlePos.Length; i++)
                {
                    g.FillEllipse(Brushes.Cyan, (float)(handlePos[i].X - OPERATOR_OFFSET / 4d), (float)(handlePos[i].Y - OPERATOR_OFFSET / 4d), (float)(OPERATOR_OFFSET / 2d), (float)(OPERATOR_OFFSET / 2d));
                }
            }
            return handlePos;
        }

        protected CvPoint[] DrawRotatedRectCore(Graphics g, Pen p, Mat imageMatrix, CvRotatedRect rotatedRect, bool drawCross = false, bool drawArrow = false, bool insideMode = false, bool drawHandle = false)
        {
            CvPoint[] rotatedRectPoints8 = CvOperator.CalcRotatedRectPoints(rotatedRect);
            CvPoint[] ps1 = new CvPoint[9];
            ps1[0] = rotatedRect.Center;
            Array.Copy(rotatedRectPoints8, 0, ps1, 1, 8);

            var ps = TransformPoints(imageMatrix, ps1);

            g.DrawLine(p, ps[1],ps[2]);
            g.DrawLine(p, ps[2], ps[3]);
            g.DrawLine(p, ps[3], ps[4]);
            g.DrawLine(p, ps[4], ps[1]);

            if (drawCross)
            {
                DrawCrossCore(g, p, null, ps[0]);
            }

            CvPoint pa = ps[7];
            if (drawArrow)
            {
                if (!insideMode)
                {
                    double length = Math.Min(CvOperator.DistancePP(ps[0], ps[7]), CvOperator.DistancePP(ps[3],ps[4]));
                    if (OPERATOR_OFFSET < length)
                    {
                        length = OPERATOR_OFFSET * 2d;
                    }
                   pa = CvOperator.MovePoint(ps[7], rotatedRect.Angle, length);
                }
                DrawLineCore(g, p, null,new CvLine(ps[0], pa), true);
            }
            CvPoint[] handlePos = null;
            if (drawHandle)
            {
                handlePos = new CvPoint[10];
                ps.CopyTo(handlePos, 0);
                handlePos[9] = pa;

                for (int i = 1; i < 9; i++)//不画最后一个箭头点
                {
                    g.FillEllipse(Brushes.Cyan, (float)(handlePos[i].X - OPERATOR_OFFSET / 4d), (float)(handlePos[i].Y - OPERATOR_OFFSET / 4d), (float)(OPERATOR_OFFSET / 2d), (float)(OPERATOR_OFFSET / 2d));
                }
            }
            return handlePos;
        }

        public class CvDispCross : CvDispObj
        {
            private CvPoint _pos;
            private double _size, _angle;

            public CvDispCross(CvPoint pos, double size = OPERATOR_OFFSET, double angle = 45d)
            {
                if (size <= 0d)
                {
                    throw new ArgumentException("size <= 0d", nameof(size));
                }
                _pos = pos;
                _size = size;
                _angle = angle;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawCrossCore(g, p, IsControlCoordSystem ? null : imageMatrix, _pos, _size, _angle);
                }
            }
        }

        public class CvDispLine : CvDispObj
        {
            private CvLine _line;

            private bool _displayArrow = false;
            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public bool DisplayArrow
            {
                get
                {
                    return _displayArrow;
                }
                set
                {
                    if (_displayArrow != value)
                    {
                        _displayArrow = value;
                        OnPropertyChanged(nameof(DisplayArrow));
                    }
                }
            }

            public CvDispLine(CvLine line)
            {
                _line = line;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawLineCore(g, p, IsControlCoordSystem ? null : imageMatrix, _line, DisplayArrow);
                }
            }
        }

        public class CvDispCircle : CvDispObj
        {
            internal CvCircle _circle;

            private double _startAngle = 0d, _sweepAngle = 360d;

            [DefaultValue(0d)]
            [RefreshProperties(RefreshProperties.All)]
            public virtual double StartAngle
            {
                get
                {
                    return _startAngle;
                }
                set
                {
                    if (_startAngle != value)
                    {
                        if (value < -360d || value > 360d)
                        {
                            _startAngle = value % 360d;
                        }
                        else
                        {
                            _startAngle = value;
                        }
                        OnPropertyChanged(nameof(StartAngle));
                    }
                }
            }

            [DefaultValue(360d)]
            [RefreshProperties(RefreshProperties.All)]
            public virtual double SweepAngle
            {
                get
                {
                    return _sweepAngle;
                }
                set
                {
                    if (_sweepAngle != value)
                    {
                        if (value > 0d)
                        {
                            _sweepAngle = value % 360d;
                        }
                        OnPropertyChanged(nameof(SweepAngle));
                    }
                }
            }

            public CvDispCircle(CvCircle circle)
            {
                if (circle.Radius <= 0d)
                {
                    throw new ArgumentException("circle.Radius <= 0d", nameof(circle));
                }
                _circle = circle;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawArcCore(g, p, IsControlCoordSystem ? null : imageMatrix, _circle, StartAngle, SweepAngle, false);
                }
            }
        }

        public class CvDispRing : CvDispCircle
        {
            private double _ringWidth;

            public CvDispRing(CvCircle circle, double ringWidth) : base(circle)
            {
                if (ringWidth <= 0d)
                {
                    throw new ArgumentException("ringWidth <= 0d", nameof(ringWidth));
                }
                _ringWidth = ringWidth;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawRingCore(g, p, IsControlCoordSystem ? null : imageMatrix, _circle, _ringWidth, StartAngle, SweepAngle);
                }
            }
        }

        public class CvDispRect : CvDispObj
        {
            private CvRect _rect;

            public CvDispRect(CvRect rect)
            {
                _rect = rect;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawRectCore(g, p, IsControlCoordSystem ? null : imageMatrix, _rect, false);
                }
            }
        }

        public class CvDispRotatedRect : CvDispObj
        {
            private CvRotatedRect _rotatedRect;
            
            private bool _displayCross = false;
            private bool _displayArrow = false;
            private bool _arrowInsideMode = false;

            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public bool DisplayCross
            {
                get
                {
                    return _displayCross;
                }
                set
                {
                    if (_displayCross != value)
                    {
                        _displayCross = value;
                        OnPropertyChanged(nameof(DisplayCross));
                    }
                }
            }

            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public bool DisplayArrow
            {
                get
                {
                    return _displayArrow;
                }
                set
                {
                    if (_displayArrow != value)
                    {
                        _displayArrow = value;
                        OnPropertyChanged(nameof(DisplayArrow));
                    }
                }
            }

            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public bool ArrowInsideMode
            {
                get
                {
                    return _arrowInsideMode;
                }
                set
                {
                    if (_arrowInsideMode != value)
                    {
                        _arrowInsideMode = value;
                        OnPropertyChanged(nameof(ArrowInsideMode));
                    }
                }
            }

            public CvDispRotatedRect(CvRotatedRect rotatedRect)
            {
                if (rotatedRect.Width <= 0d || rotatedRect.Height <= 0d)
                {
                    throw new ArgumentException("rotatedRect.Width <= 0d || rotatedRect.Height <= 0d", nameof(rotatedRect));
                }
                _rotatedRect = rotatedRect;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    DrawRotatedRectCore(g, p, IsControlCoordSystem ? null : imageMatrix, _rotatedRect, DisplayCross, DisplayArrow, ArrowInsideMode);
                }
            }
        }

        public class CvDispText : CvDispObj
        {
            private CvPoint _pos;
            private string _text;

            private FontStyle _fontStyle = FontStyle.Regular;
            private float _fontSize = 9f;
            private string _fontName = "Arial";
            private bool _border = false;
            private Color _borderColor = Color.Orange;
            private Color _backColor = Color.LightYellow;

            [DefaultValue(FontStyle.Regular)]
            [RefreshProperties(RefreshProperties.All)]
            public FontStyle FontStyle
            {
                get
                {
                    return _fontStyle;
                }
                set
                {
                    if (_fontStyle != value)
                    {
                        _fontStyle = value;
                        OnPropertyChanged(nameof(FontStyle));
                    }
                }
            }

            [DefaultValue(9f)]
            [RefreshProperties(RefreshProperties.All)]
            public float FontSize
            {
                get
                {
                    return _fontSize;
                }
                set
                {
                    if (_fontSize != value)
                    {
                        if (value < 9f)
                        {
                            _fontSize = 9f;
                        }
                        else
                        {
                            _fontSize = value;
                        }
                        OnPropertyChanged(nameof(FontSize));
                    }
                }
            }

            [DefaultValue("Arial")]
            [RefreshProperties(RefreshProperties.All)]
            public string FontName
            {
                get
                {
                    return _fontName;
                }
                set
                {
                    if (_fontName != value)
                    {
                        string[] fonts = QueryFontNames();
                        foreach (string f in fonts)
                        {
                            if (string.Equals(f, value, StringComparison.InvariantCultureIgnoreCase))
                            {
                                _fontName = f;
                                OnPropertyChanged(nameof(FontName));
                                return;
                            }
                        }
                        throw new ArgumentException("Invalid font", nameof(FontName));
                    }
                }
            }

            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public bool Border
            {
                get
                {
                    return _border;
                }
                set
                {
                    if (_border != value)
                    {
                        _border = value;
                        OnPropertyChanged(nameof(Border));
                    }
                }
            }

            [DefaultValue(typeof(Color), "Orange")]
            [RefreshProperties(RefreshProperties.All)]
            public Color BorderColor
            {
                get
                {
                    return _borderColor;
                }
                set
                {
                    if (_borderColor != value)
                    {
                        _borderColor = value;
                        OnPropertyChanged(nameof(BorderColor));
                    }
                }
            }

            [DefaultValue(typeof(Color), "LightYellow")]
            [RefreshProperties(RefreshProperties.All)]
            public Color BackColor
            {
                get
                {
                    return _backColor;
                }
                set
                {
                    if (_backColor != value)
                    {
                        _backColor = value;
                        OnPropertyChanged(nameof(BackColor));
                    }
                }
            }

            [EditorBrowsable(EditorBrowsableState.Advanced)]
            public static string[] QueryFontNames()
            {
                using (System.Drawing.Text.InstalledFontCollection fonts = new System.Drawing.Text.InstalledFontCollection())
                {
                    string[] ret = new string[fonts.Families.Length];
                    for (int i = 0; i < ret.Length; i++)
                    {
                        ret[i] = fonts.Families[i].Name;
                    }
                    return ret;
                }
            }

            public CvDispText(string text, CvPoint pos)
            {
                if (string.IsNullOrEmpty(text))
                {
                    throw new ArgumentException("string.IsNullOrEmpty(text)", nameof(text));
                }
                _text = text;
                _pos = pos;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Font font = new Font(FontName, FontSize, FontStyle))
                using (Brush foreBrush = new SolidBrush(Color))
                {
                    if (Border)
                    {
                        var fontSize = g.MeasureString(_text, font);
                        using (Brush backBrush = new SolidBrush(BackColor))
                        using (Pen borderPen = new Pen(BorderColor, PenWidth))
                        {
                            if (IsControlCoordSystem)
                            {
                                g.FillRectangle(backBrush, (float)_pos.X, (float)_pos.Y, fontSize.Width, fontSize.Height);
                                g.DrawRectangle(borderPen, (float)_pos.X, (float)_pos.Y, fontSize.Width, fontSize.Height);
                                g.DrawString(_text, font, foreBrush, (float)_pos.X, (float)_pos.Y);
                            }
                            else
                            {
                                var ps = TransformPoints(imageMatrix, _pos);
                                g.FillRectangle(backBrush, (float)ps[0].X, (float)ps[0].Y, fontSize.Width, fontSize.Height);
                                g.DrawRectangle(borderPen, (float)ps[0].X, (float)ps[0].Y, fontSize.Width, fontSize.Height);
                                g.DrawString(_text, font, foreBrush, (float)ps[0].X, (float)ps[0].Y);
                            }
                        }
                    }
                    else
                    {
                        if (IsControlCoordSystem)
                        {
                            g.DrawString(_text, font, foreBrush, (float)_pos.X, (float)_pos.Y);
                        }
                        else
                        {
                            var ps = TransformPoints(imageMatrix, _pos);
                            g.DrawString(_text, font, foreBrush, (float)ps[0].X, (float)ps[0].Y);
                        }
                    }
                }
            }
        }
    }

    public abstract class CvDrawObj : CvDispObj
    {
        protected bool IsCapture(double x1, double y1, double x2, double y2)
        {
            return CvOperator.DistancePP(x1, y1, x2, y2) <= OPERATOR_OFFSET;
        }

        internal CvPoint[] _dragPoints;
        internal int _dragIndex = -1;
        internal CvPoint _dragPos = new CvPoint(0d, 0d);

        public virtual void OnMouseDown(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
        {
            _dragIndex = -1;
            if (IsControlCoordSystem)
            {
                _dragPos.X = ctlX;
                _dragPos.Y = ctlY;
            }
            else
            {
                _dragPos.X = imgX;
                _dragPos.Y = imgY;
            }
            for (int i = 0; i < _dragPoints.Length; i++)
            {
                if (IsCapture(ctlX, ctlY, _dragPoints[i].X, _dragPoints[i].Y))
                {
                    _dragIndex = i;
                    handled = true;
                    repaint = true;
                    break;
                }
            }
        }
        public abstract void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled);
        public virtual void OnMouseUp(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
        {
            _dragIndex = -1;
        }

        public class CvDrawLine : CvDrawObj
        {
            private CvLine _line;

            [RefreshProperties(RefreshProperties.All)]
            public CvLine Line
            {
                get
                {
                    return _line;
                }
                set
                {
                    if (_line != value)
                    {
                        _line = value;
                        OnPropertyChanged(nameof(Line));
                    }
                }
            }

            public CvDrawLine(CvLine line)
            {
                _line = line;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    _dragPoints = DrawLineCore(g, p, IsControlCoordSystem ? null : imageMatrix, Line, true);
                }
            }

            public override void OnMouseDown(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                base.OnMouseDown(ctlX, ctlY, imgX, imgY, ref repaint, ref handled);
                if (_dragIndex < 0)
                {
                    if (CvOperator.DistancePl(ctlX, ctlY, _dragPoints[0].X, _dragPoints[0].Y, _dragPoints[1].X, _dragPoints[1].Y) <= OPERATOR_OFFSET)
                    {
                        _dragIndex = 2;
                        handled = true;
                        repaint = true;
                    }
                }
            }

            public override void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                if (_dragIndex < 0)
                {
                    return;
                }
                double dx, dy;
                if (IsControlCoordSystem)
                {
                    dx = ctlX - _dragPos.X;
                    dy = ctlX - _dragPos.Y;
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                }
                else
                {
                    dx = imgX - _dragPos.X;
                    dy = imgY - _dragPos.Y;
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                }
                CvLine tmpLine = Line;
                switch (_dragIndex)
                {
                    case 0:
                        tmpLine.BeginPoint = new CvPoint(Line.BeginX + dx, Line.BeginY + dy);
                        break;
                    case 1:
                        tmpLine.EndPoint = new CvPoint(Line.EndX + dx, Line.EndY + dy);
                        break;
                    case 2:
                        tmpLine.BeginPoint = new CvPoint(Line.BeginX + dx, Line.BeginY + dy);
                        tmpLine.EndPoint = new CvPoint(Line.EndX + dx, Line.EndY + dy);
                        break;
                }
                Line = tmpLine;
                repaint = true;
                handled = true;
            }
        }

        public class CvDrawCircle : CvDrawObj
        {
            private CvCircle _circle;

            private double _startAngle = 0d, _sweepAngle = 360d;
            private bool _angleEnabled = false;

            [RefreshProperties(RefreshProperties.All)]
            public virtual CvCircle Circle
            {
                get
                {
                    return _circle;
                }
                set
                {
                    if (_circle != value && value.Radius > 0d)
                    {
                        _circle = value;
                        OnPropertyChanged(nameof(Circle));
                    }
                }
            }

            [DefaultValue(0d)]
            [RefreshProperties(RefreshProperties.All)]
            public virtual double StartAngle
            {
                get
                {
                    return _startAngle;
                }
                set
                {
                    if (_startAngle != value)
                    {
                        if (value < -360d || value > 360d)
                        {
                            _startAngle = value % 360d;
                        }
                        else
                        {
                            _startAngle = value;
                        }
                        OnPropertyChanged(nameof(StartAngle));
                    }
                }
            }

            [DefaultValue(360d)]
            [RefreshProperties(RefreshProperties.All)]
            public virtual double SweepAngle
            {
                get
                {
                    return _sweepAngle;
                }
                set
                {
                    if (_sweepAngle != value)
                    {
                        if (value > 0d)
                        {
                            _sweepAngle = value % 360d;
                        }
                        OnPropertyChanged(nameof(SweepAngle));
                    }
                }
            }

            [DefaultValue(false)]
            [RefreshProperties(RefreshProperties.All)]
            public virtual bool AngleEnabled
            {
                get
                {
                    return _angleEnabled;
                }
                set
                {
                    if (_angleEnabled != value)
                    {
                        _angleEnabled = value;
                        OnPropertyChanged(nameof(AngleEnabled));
                    }
                }
            }

            public CvDrawCircle(CvCircle circle)
            {
                if (circle.Radius <= 0d)
                {
                    throw new ArgumentException("circle.Radius <= 0d", nameof(circle));
                }
                _circle = circle;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    _dragPoints = DrawArcCore(g, p, IsControlCoordSystem ? null : imageMatrix, Circle, StartAngle, SweepAngle, true);
                }
            }

            public override void OnMouseDown(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                _dragIndex = -1;
                if (IsControlCoordSystem)
                {
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                }
                else
                {
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                }
                for (int i = _dragPoints.Length - 1; i >= 0; i--)
                {
                    if (IsCapture(ctlX, ctlY, _dragPoints[i].X, _dragPoints[i].Y))
                    {
                        _dragIndex = i;
                        repaint = true;
                        handled = true;
                        break;
                    }
                }
            }

            public override void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                if (_dragIndex < 0)
                {
                    return;
                }
                double dx, dy, ds, da = 0d;
                if (IsControlCoordSystem)
                {
                    dx = ctlX - _dragPos.X;
                    dy = ctlX - _dragPos.Y;
                    ds = CvOperator.DistancePP(Circle.CenterX,Circle.CenterY, ctlX, ctlY);
                    if (_angleEnabled)
                    {
                        da = CvOperator.LineOrientation(Circle.CenterX, Circle.CenterY, ctlX, ctlY);
                    }
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                }
                else
                {
                    dx = imgX - _dragPos.X;
                    dy = imgY - _dragPos.Y;
                    ds = CvOperator.DistancePP(Circle.CenterX, Circle.CenterY, imgX, imgY);
                    if (_angleEnabled)
                    {
                        da = CvOperator.LineOrientation(Circle.CenterX, Circle.CenterY, imgX, imgY);
                    }
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                }
                CvCircle tmpCircle = Circle;
                switch (_dragIndex)
                {
                    case 0:
                        tmpCircle.Center = new CvPoint(Circle.CenterX + dx, Circle.CenterY + dy);
                        break;
                    case 1:
                        tmpCircle.Radius = ds;
                        if (AngleEnabled)
                        {
                            StartAngle = da;
                        }
                        break;
                    case 2:
                        tmpCircle.Radius = ds;
                        if (AngleEnabled)
                        {
                            if (da > StartAngle)
                            {
                                SweepAngle = da - StartAngle;
                            }
                            else
                            {
                                SweepAngle = 360d + da - StartAngle;
                            }
                        }
                        break;
                }
                Circle = tmpCircle;
                repaint = true;
                handled = true;
            }
        }

        public class CvDrawRing : CvDrawCircle
        {
            private double _ringWidth;

            [RefreshProperties(RefreshProperties.All)]
            public virtual double RingWidth
            {
                get
                {
                    return _ringWidth;
                }
                set
                {
                    if (_ringWidth != value)
                    {
                        if (value < Circle.Radius * 2d)
                        {
                            _ringWidth = value;
                            OnPropertyChanged(nameof(RingWidth));
                        }
                    }
                }
            }

            public override CvCircle Circle
            {
                get
                {
                    return base.Circle;
                }
                set
                {
                    if (value.Radius > RingWidth)
                    {
                        base.Circle = value;
                    }
                }
            }

            public CvDrawRing(CvCircle circle, double ringWidth) : base(circle)
            {
                if (ringWidth <= 0d)
                {
                    throw new ArgumentException("ringWidth <= 0d", nameof(ringWidth));
                }
                _ringWidth = ringWidth;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    _dragPoints = DrawRingCore(g, p, IsControlCoordSystem ? null : imageMatrix, Circle, RingWidth, StartAngle, SweepAngle, true);
                }
            }

            public override void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                if (_dragIndex < 0)
                {
                    return;
                }

                double dx, dy, ds, da = 0d;
                if (IsControlCoordSystem)
                {
                    dx = ctlX - _dragPos.X;
                    dy = ctlX - _dragPos.Y;
                    ds = CvOperator.DistancePP(Circle.CenterX, Circle.CenterY, ctlX, ctlY);
                    if (AngleEnabled)
                    {
                      da =  CvOperator.LineOrientation(Circle.CenterX, Circle.CenterY, ctlX, ctlY);
                    }
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                }
                else
                {
                    dx = imgX - _dragPos.X;
                    dy = imgY - _dragPos.Y;
                    ds = CvOperator.DistancePP(Circle.CenterX, Circle.CenterY, imgX, imgY);
                    if (AngleEnabled)
                    {
                      da =  CvOperator.LineOrientation(Circle.CenterX, Circle.CenterY, imgX, imgY);
                    }
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                }

                double r1 =Circle.Radius - RingWidth / 2d;
                double r2 = r1 + RingWidth;
                CvCircle tmpCircle = Circle;
                switch (_dragIndex)
                {
                    case 0:
                        tmpCircle.Center = new CvPoint(Circle.CenterX + dx, Circle.CenterY + dy);
                        break;
                    case 1:
                    case 3:
                        r1 = ds;
                        if (r1 < r2)
                        {
                            RingWidth = r2 - r1;
                        }
                        break;
                    case 2:
                    case 4:
                        r2 = ds;
                        if (r1 < r2)
                        {
                            RingWidth = r2 - r1;
                        }
                        break;
                    case 5:
                        tmpCircle.Radius = ds;
                        if (AngleEnabled)
                        {
                            StartAngle = da;
                        }
                        break;
                    case 6:
                        tmpCircle.Radius = ds;
                        if (AngleEnabled)
                        {
                            if (da > StartAngle)
                            {
                                SweepAngle = da - StartAngle;
                            }
                            else
                            {
                                SweepAngle = 360d + da - StartAngle;
                            }
                        }
                        break;
                }
                Circle = tmpCircle;
                repaint = true;
                handled = true;
            }
        }

        public class CvDrawRect : CvDrawObj
        {
            private CvRect _rect;

            [RefreshProperties(RefreshProperties.All)]
            public virtual CvRect Rect
            {
                get
                {
                    return _rect;
                }
                set
                {
                    if (value != _rect && value.Width > 0d && value.Height > 0d)
                    {
                        _rect = value;
                        OnPropertyChanged(nameof(Rect));
                    }
                }
            }

            public CvDrawRect(CvRect rect)
            {
                if (rect.Width < 0d || rect.Height < 0d)
                {
                    throw new ArgumentException("rect.Width < 0d || rect.Height < 0d", nameof(rect));
                }
                _rect = rect;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    _dragPoints = DrawRectCore(g, p, IsControlCoordSystem ? null : imageMatrix, Rect, true);
                }
            }

            public override void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                if (_dragIndex < 0)
                {
                    return;
                }

                double dx, dy;
                if (IsControlCoordSystem)
                {
                    dx = ctlX - _dragPos.X;
                    dy = ctlY - _dragPos.Y;
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                }
                else
                {
                    dx = imgX - _dragPos.X;
                    dy = imgY - _dragPos.Y;
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                }

                switch (_dragIndex)
                {
                    case 0:
                        Rect = new CvRect(Rect.X + dx, Rect.Y + dy, Rect.Width, Rect.Height);
                        break;
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                        double x = Rect.X + Rect.Width / 2d;
                        double y = Rect.Y + Rect.Height / 2d;
                        double w, h;

                        if (IsControlCoordSystem)
                        {
                            w = CvOperator.DistancePl(ctlX, ctlY, x, -1d, x, 1d);
                            h = CvOperator.DistancePl(ctlX, ctlY, -1d, y, 1d, y);
                        }
                        else
                        {
                            w = CvOperator.DistancePl(imgX, imgY, x, -1d, x, 1d);
                            h = CvOperator.DistancePl(imgX, imgY, -1d, y, 1d, y);
                        }
                        Rect = new CvRect(x - w, y - h, w * 2d, h * 2d);
                        break;
                }

                repaint = true;
                handled = true;
            }
        }

        public class CvDrawRotatedRect : CvDrawObj
        {
            private CvRotatedRect _rotatedRect;

            [RefreshProperties(RefreshProperties.All)]
            public virtual CvRotatedRect RotatedRect
            {
                get
                {
                    return _rotatedRect;
                }
                set
                {
                    if (_rotatedRect != value)
                    {
                        _rotatedRect = value;
                        OnPropertyChanged(nameof(RotatedRect));
                    }
                }
            }

            public CvDrawRotatedRect(CvRotatedRect rotatedRect)
            {
                if (rotatedRect.Width <= 0d || rotatedRect.Height <= 0d)
                {
                    throw new ArgumentException("rotatedRect.Width <= 0d || rotatedRect.Height <= 0d", nameof(rotatedRect));
                }
                _rotatedRect = rotatedRect;
            }

            public override void OnPaint(Graphics g, Mat imageMatrix)
            {
                using (Pen p = new Pen(Color, PenWidth))
                {
                    _dragPoints = DrawRotatedRectCore(g, p, IsControlCoordSystem ? null : imageMatrix, _rotatedRect, true, true, false, true);
                }
            }

            public override void OnMouseMove(double ctlX, double ctlY, double imgX, double imgY, ref bool repaint, ref bool handled)
            {
                if (_dragIndex < 0)
                {
                    return;
                }

                double dx, dy, x, y;
                if (IsControlCoordSystem)
                {
                    dx = ctlX - _dragPos.X;
                    dy = ctlY - _dragPos.Y;
                    _dragPos.X = ctlX;
                    _dragPos.Y = ctlY;
                    x = ctlX;
                    y = ctlY;
                }
                else
                {
                    dx = imgX - _dragPos.X;
                    dy = imgY - _dragPos.Y;
                    _dragPos.X = imgX;
                    _dragPos.Y = imgY;
                    x = imgX;
                    y = imgY;
                }

                CvPoint[] ps = CvOperator.CalcRotatedRectPoints(RotatedRect);
                CvRotatedRect tmpRotatedRect = RotatedRect;
                switch (_dragIndex)
                {
                    case 0:
                        tmpRotatedRect.Center = new CvPoint(tmpRotatedRect.CenterX+ dx, tmpRotatedRect.CenterY + dy);
                        break;
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                        tmpRotatedRect.Size = new Size2d(2d * CvOperator.DistancePl(x, y, ps[5].X, ps[5].Y, ps[7].X, ps[7].Y), 2d * CvOperator.DistancePl(x, y, ps[4].X, ps[4].Y, ps[6].X, ps[6].Y));
                        break;
                    case 5:
                    case 7:
                        tmpRotatedRect.Size = new Size2d(2d * CvOperator.DistancePl(x, y, ps[5].X, ps[5].Y, ps[7].X, ps[7].Y), RotatedRect.Height);
                        break;
                    case 6:
                    case 8:
                        tmpRotatedRect.Size = new Size2d(RotatedRect.Width, 2d * CvOperator.DistancePl(x, y, ps[4].X, ps[4].Y, ps[6].X, ps[6].Y));
                        break;
                    case 9:
                        tmpRotatedRect.Angle = CvOperator.LineOrientation(tmpRotatedRect.CenterX, tmpRotatedRect.CenterY, x, y);
                        break;
                }
                RotatedRect = tmpRotatedRect;
                repaint = true;
                handled = true;
            }
        }
    }
}